package com.lzc.tlmail.tlmailseckill.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.lzc.common.to.mq.Seckillto;
import com.lzc.common.utils.R;
import com.lzc.common.vo.MemberEntityVo;
import com.lzc.tlmail.tlmailseckill.feign.CouponFeignService;
import com.lzc.tlmail.tlmailseckill.feign.ProductFeignService;
import com.lzc.tlmail.tlmailseckill.interceptor.SeckillInterceptor;
import com.lzc.tlmail.tlmailseckill.service.SeckillService;
import com.lzc.tlmail.tlmailseckill.to.SeckillSkusInfoto;
import com.lzc.tlmail.tlmailseckill.vo.SeckillSessionvo;
import com.lzc.tlmail.tlmailseckill.vo.SeckillSkuRelationvo;
import com.lzc.tlmail.tlmailseckill.vo.SkuInfoVo;

import org.redisson.api.RSemaphore;
import org.redisson.api.RedissonClient;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * @author lzc
 * @create 2020-10-24 15:50
 */
@Service
public class SeckillServiceImpl implements SeckillService {
    //秒杀场次的键
    private final  String SESSION_INFO_PREFIX="seckill_Info:";
    //秒杀场次具体商品信息的键
    private final  String SESSIONSKU_INFO_PREFIX="seckill_SkuInfo:";
    //秒杀库存信号量
    private final  String SESSIONSKU_SEMAPHORE="seckill_stock:";

    @Autowired
    private CouponFeignService couponFeignService;
    @Autowired
    private StringRedisTemplate redisTemplate;
    @Autowired
    private ProductFeignService productFeignService;
    @Autowired
    private RedissonClient redissonClient;
    @Autowired
    private RabbitTemplate rabbitTemplate;
    /**
     * 每日三点上架最近三天的商品
     * 实现思路:先查出对应数据，查出来的秒杀场次每一次都保存到redis中
     * 然后每场关联的sku信息都要保存进去 保存的有秒杀的信息 还有这次秒杀对应的商品信息
     * 例如 这次秒杀的商品是 华为p20  那就有对应的秒杀哦数量  价格 时间等 信息  再加上具体自己的商品信息
     * 后面还要保证安全 使用了 信号量加一个随机码
     */
    @Override
    public void uploadSeckillSkuLatest3Days() {
        R r = couponFeignService.uploadSeckillSkuLatest3Days();
        if(r.getCode()==0){
            List<SeckillSessionvo> data = r.getData("data", new TypeReference<List<SeckillSessionvo>>(){});
            if(data!=null && data.size()>0){
                //缓存redis
                //1 秒杀的信息   用list
                saveSessionInfos(data);
                //2 秒杀关联的商品信息   用hash
                saveSessionInfosWithSkus(data);
            }

        }
    }

    /**
     * 获取当前参与秒杀的活动
     * @return
     */
    @Override
    public List<SeckillSkusInfoto> getSeckillSessionSkus() {
        Long time = new Date().getTime();
        Set<String> keys = redisTemplate.keys(SESSION_INFO_PREFIX + "*");
        if(keys!=null&&keys.size()>0){
            for (String key : keys) {
                //seckill_Info:1603670400000_1603846800000
                if(!StringUtils.isEmpty(key)){
                    String replace = key.replace(SESSION_INFO_PREFIX, "");
                    String[] s = replace.split("_");
                    Long l = Long.parseLong(s[0]);
                    Long l1 = Long.parseLong(s[1]);
                    if(time>=l&&time<=l1){
                        List<String> range = redisTemplate.opsForList().range(key, -100, 100);
                        BoundHashOperations<String, String, String> hashOps = redisTemplate.boundHashOps(SESSIONSKU_INFO_PREFIX);
                        List<String> strings = hashOps.multiGet(range);
                        if(strings!=null&&strings.size()>0){
                            List<SeckillSkusInfoto> collect = strings.stream().map(item -> {
                                return JSON.parseObject(item, SeckillSkusInfoto.class);
                            }).collect(Collectors.toList());
                            return collect;
                        }
                        break;
                    }
                }
            }
        }

        return null;
    }

    /**
     * 获取sku秒杀信息
     * @param skuId
     * @return
     */
    @Override
    public SeckillSkusInfoto getSeckillSkuInfo(Long skuId) {
        BoundHashOperations<String, String, String> hashOps = redisTemplate.boundHashOps(SESSIONSKU_INFO_PREFIX);
        Set<String> keys = hashOps.keys();
        if(keys!=null&&keys.size()>0){
            String regx="\\d_"+skuId;
            for (String key : keys) {
                if(Pattern.matches(regx,key)){
                    String s = hashOps.get(key);
                    SeckillSkusInfoto seckillSkusInfoto = JSON.parseObject(s, SeckillSkusInfoto.class);
                    long now = new Date().getTime();
                    if(now>=seckillSkusInfoto.getStartTime()&&now<=seckillSkusInfoto.getEndTime()){

                    }else{
                        seckillSkusInfoto.setRandomCode(null);
                    }
                    return seckillSkusInfoto;
                }
            }
        }

        return null;
    }

    /**
     * 秒杀商品
     * @param killId
     * @param num
     * @param code
     * @return
     */
    @Override
    public String skuKill(String killId, Integer num, String code) {
        MemberEntityVo memberEntityVo = SeckillInterceptor.userInfotoThreadLocal.get();
        BoundHashOperations<String, String, String> hashOps = redisTemplate.boundHashOps(SESSIONSKU_INFO_PREFIX);
        //得到秒杀的商品
        String sku = hashOps.get(killId);
        //验证存不存在
        if(!StringUtils.isEmpty(sku)){
            SeckillSkusInfoto seckillSkusInfoto = JSON.parseObject(sku, SeckillSkusInfoto.class);
            long time = new Date().getTime();
            //验证是否是秒杀时间
            if(time>=seckillSkusInfoto.getStartTime()&&time<=seckillSkusInfoto.getEndTime()){
                //验证秒杀数量
                if(num<=seckillSkusInfoto.getSeckillLimit().intValue()){
                    String seckid = seckillSkusInfoto.getPromotionSessionId() + "_" + seckillSkusInfoto.getSkuId();
                    //验证随机码对不对
                    if(code.equals(seckillSkusInfoto.getRandomCode())&&seckid.equals(killId)){
                        //占坑 防止重复秒杀
                        String keng = memberEntityVo.getId() + seckid;
                        Boolean aBoolean = redisTemplate.opsForValue().setIfAbsent(keng, num.toString(), seckillSkusInfoto.getEndTime() - time, TimeUnit.MILLISECONDS);
                        if(aBoolean){
                            RSemaphore semaphore = redissonClient.getSemaphore(SESSIONSKU_SEMAPHORE + code);
                            semaphore.tryAcquire();
                            String ordersn = UUID.randomUUID().toString().replace("_", "");
                            //发送消息
                            Seckillto seckillto = new Seckillto();
                            seckillto.setOrdersn(ordersn);
                            seckillto.setNum(num);
                            seckillto.setPromotionSessionId(seckillSkusInfoto.getPromotionSessionId());
                            BigDecimal price = seckillSkusInfoto.getSeckillPrice().multiply(new BigDecimal(num.toString()));
                            seckillto.setSeckillPrice(price);
                            seckillto.setSkuId(seckillSkusInfoto.getSkuId());
                            rabbitTemplate.convertAndSend("order-event-exchange","order.seckill.order",seckillto);
                            return ordersn;
                        }
                    }
                }
            }
        }
        return null;
    }

    /**
     * 保存秒杀信息 先报错这整场的所有秒杀信息
     * @param data
     */
    private void saveSessionInfosWithSkus(List<SeckillSessionvo> data) {
        data.stream().forEach(session->{
            Long startTime = session.getStartTime().getTime();
            Long time = session.getEndTime().getTime();
            String key = SESSION_INFO_PREFIX + startTime + "_" + time;
            if(!redisTemplate.hasKey(key)){
                List<String> collect = session.getSkuRelationEntityList().stream().map(item -> item.getId().toString()+"_"+item.getSkuId().toString()).collect(Collectors.toList());
                redisTemplate.opsForList().leftPushAll(key,collect);
            }
        });
    }

    /**
     * 再是保存这场具体的每一个商品的具体秒杀细节和详细商品信息了
     * @param data
     */
    private void saveSessionInfos(List<SeckillSessionvo> data) {

        data.stream().forEach(session->{
                    BoundHashOperations<String, Object, Object> ops = redisTemplate.boundHashOps(SESSIONSKU_INFO_PREFIX);
                    for (SeckillSkuRelationvo sku : session.getSkuRelationEntityList()) {
                        if(!ops.hasKey(sku.getId().toString()+"_"+sku.getSkuId().toString())){
                            SeckillSkusInfoto seckillSkusInfoto = new SeckillSkusInfoto();
                            //1秒杀基本信息
                            BeanUtils.copyProperties(sku, seckillSkusInfoto);

                            //2商品详细信息
                            R skuInfo = productFeignService.getSkuInfo(sku.getSkuId());
                            if (skuInfo.getCode() == 0) {
                                SkuInfoVo skuInfoData = skuInfo.getData("data", new TypeReference<SkuInfoVo>() {
                                });
                                seckillSkusInfoto.setSkuInfoVo(skuInfoData);
                            }
                            //3 商品的秒杀时间
                            seckillSkusInfoto.setStartTime(session.getStartTime().getTime());
                            seckillSkusInfoto.setEndTime(session.getEndTime().getTime());
                            //4 商品随机码 防止秒杀没有按正常操作 防止恶意操作 这里设置了之后只有带随机码才能秒杀商品
                            String randomUUID = UUID.randomUUID().toString().replace("_", "");
                            seckillSkusInfoto.setRandomCode(randomUUID);
                            //5设置信号量 设置只能秒杀指定件数 限流
                            RSemaphore semaphore = redissonClient.getSemaphore(SESSIONSKU_SEMAPHORE + randomUUID);
                            semaphore.trySetPermits(sku.getSeckillCount().intValue());
                            String s = JSON.toJSONString(seckillSkusInfoto);
                            ops.put(sku.getId().toString()+"_"+sku.getSkuId().toString(), s);
                        }

                    }
                }
        );

    }
}
